using Fusion.Async.Threads;
using Fusion.Async.Timers;
using Fusion.Utilities.Bit;
using Fusion.Utilities.Generics;
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;

namespace Fusion.Net.UDP
{
    public class ReliableOrderUDPClientSocket : UDPSocket
    {
        OrderDictionary<string, DataCache> dataCaches = new OrderDictionary<string, DataCache>();
        OrderDictionary<string, DataCache> sendCaches = new OrderDictionary<string, DataCache>();
        OrderDictionary<string, ConcurrentQueue<byte[]>> busyCaches = new OrderDictionary<string, ConcurrentQueue<byte[]>>();
        OrderDictionary<string, HeartBeatsChecker> heartBeatsCheckers = new OrderDictionary<string, HeartBeatsChecker>();
        private float heartBeatsCheckerInterval = -1;
        private int packedOffset = 5;
        List<byte[]> splitedDataList = new List<byte[]>();
        ThreadData sendConfirmTimer;

        public ReliableOrderUDPClientSocket(UDPNet udp, AddressFamily addressFamily) : base(udp, addressFamily)
        {
            StartSendConfirmThread();
        }

        public ReliableOrderUDPClientSocket(UDPNet udp, Socket socket) : base(udp, socket)
        {
            StartSendConfirmThread();
        }

        private void StartSendConfirmThread()
        {
            sendConfirmTimer = new ThreadData(SendConfirm, ThreadType.Timer, false);
            sendConfirmTimer.Start(100, 100);
        }

        private void SendConfirm()
        {
            lock (sendCaches)
            {
                int count = sendCaches.Count;
                for (int index = 0; index < count; index++)
                {
                    DataCache sendCache = sendCaches.Get(index);
                    lock (sendCache)
                    {
                        if (sendCache.Seq < sendCache.MaxSeq)
                        {
                            for (int i = sendCache.Seq; i < sendCache.MaxSeq; i++)
                            {
                                Send(PackExcution(3, i), sendCache.IP);
                            }
                        }
                    }
                }
            }
        }

        private byte[][] excutionDatas =
        {
            new byte[5]{1, 0, 0, 0, 0 },
            new byte[5]{2, 0, 0, 0, 0 },
            new byte[5]{3, 0, 0, 0, 0 },
        };

        private byte[] PackExcution(byte excution, int seq)
        {
            byte[] result = excutionDatas[excution - 1];
            BitUtility.WriteInt(result, 1, seq);
            return result;
        }

        protected override void Invoke()
        {
            EndPoint receiveIP = new IPEndPoint(Socket.AddressFamily == AddressFamily.InterNetworkV6 ? IPAddress.IPv6Any : IPAddress.Any, 0);
            int length = Socket.ReceiveFrom(buffer, ref receiveIP);
            byte[] data = new byte[length];
            Array.Copy(buffer, 0, data, 0, length);
            UnPack(data, receiveIP);
        }

        public List<byte[]> SplitData(byte[] data, int length)
        {
            splitedDataList.Clear();
            int realBufferSize = bufferSize - packedOffset;
            int count = length / realBufferSize;
            int offset = 0;
            for (int index = 0; index < count; index++)
            {
                byte[] splitedData = new byte[bufferSize];
                Array.Copy(data, offset, splitedData, packedOffset, realBufferSize);
                offset += realBufferSize;

                splitedDataList.Add(splitedData);
            }

            int finalDataLength = length - offset;
            byte[] finalData = new byte[finalDataLength + packedOffset];
            Array.Copy(data, offset, finalData, packedOffset, finalDataLength);

            splitedDataList.Add(finalData);
            return splitedDataList;
        }

        public void HandleBusyCache(DataCache sendCache)
        {
            ConcurrentQueue<byte[]> busyCache = GetBusyCache(sendCache.IP);

            if (busyCache != null)
            {
                lock (busyCache)
                {
                    if (sendCache.MaxSeq == int.MaxValue)
                    {
                        sendCache.Reset();
                    }
                    int freeNum = sendCache.GetFreeNum();

                    int busyCount = busyCache.Count;
                    if (busyCount > 0 && freeNum > 0)
                    {
                        int realNum = freeNum > busyCount ? (int)busyCount : freeNum;
                        for (int i = 0; i < realNum; i++)
                        {
                            byte[] oldData;
                            if (busyCache.TryDequeue(out oldData))
                            {
                                SendToCache(oldData, sendCache);
                            }
                        }
                    }
                }
            }
        }

        private byte[] PackData(byte[] data)
        {
            data[0] = 0;
            return data;
        }

        private void SendOrigin(byte[] data, int length)
        {
            if (TargetIP == null)
                return;

            SetHeartBeatsChecker(TargetIP);
            DataCache sendCache = GetSendCache(TargetIP);
            GetDataCache(TargetIP);
            ConcurrentQueue<byte[]> busyCache = GetBusyCache(TargetIP);

            List<byte[]> dataList = SplitData(data, length);

            for (int dataIndex = 0; dataIndex < dataList.Count; dataIndex++)
            {
                lock (sendCache)
                {
                    if (sendCache.CheckFull())
                    {
                        int maxSeq = sendCache.MaxSeq;
                        int selfSeq = maxSeq == int.MaxValue ? 0 : maxSeq;
                        lock (busyCache)
                        {
                            if (!busyCache.IsEmpty)
                            {
                                byte[] last = busyCache.Last();
                                int lastSeq = BitUtility.ReadInt(last, 1);
                                selfSeq = lastSeq == int.MaxValue ? 0 : lastSeq + 1;
                            }
                            byte[] splitedData = PackData(PackInt(dataList[dataIndex], selfSeq));

                            busyCache.Enqueue(splitedData);
                        }
                    }
                    else
                    {
                        byte[] splitedData = PackData(PackInt(dataList[dataIndex], sendCache.MaxSeq));
                        SendToCache(splitedData, sendCache);
                    }
                }
            }
        }

        public override void Send(byte[] data, int length)
        {
            int packedLength = length + 5;
            byte[] sendData = ArrayPool<byte>.Shared.Rent(packedLength);
            Array.Copy(data, 0, sendData, 5, length);
            SendOrigin(Pack(sendData, length), packedLength);
            ArrayPool<byte>.Shared.Return(sendData);
        }

        private void SendToCache(byte[] data, DataCache sendCache)
        {
            int seq = BitUtility.ReadInt(data, 1);
            sendCache.Set(seq, data);
            Send(data, TargetIP);
        }

        private byte[] PackInt(byte[] data, int value)
        {
            BitUtility.WriteInt(data, 1, value);
            return data;
        }

        public override byte[] Pack(byte[] data, int length)
        {
            data[4] = 0;
            BitUtility.WriteInt(data, 0, length);
            return data;
        }

        public void UnPack(byte[] data, EndPoint ip)
        {
            byte excution = data[0];
            int seq = BitUtility.ReadInt(data, 1);
            string key = ip.ToString();
            if (excution == 0) // 数据传输
            {
                //UnityEngine.Debug.LogWarning("0000RecvSeq: " + seq);
                GetSendCache(ip);
                DataCache dataCache = GetDataCache(ip);
                
                if (dataCache.Seq == int.MaxValue)
                {
                    if (seq == 0)
                    {
                        dataCache.Reset();
                    }
                }
                dataCache.Set(seq, data);

                for (int i = dataCache.Seq; i < dataCache.MaxSeq; i++)
                {
                    if (dataCache.CheckSeqEmpty(i))
                    {
                        //Debug.Log("-----" + i);
                        Send(PackExcution(2, i), dataCache.IP);
                    }
                }

                HeartBeatsChecker heartBeatsChecker = GetHeartBeatsChecker(key);
                heartBeatsChecker?.Reset();

                if (dataCache.MaxSeq > dataCache.Seq)
                {
                    int oldSeq = dataCache.Seq;
                    int realBufferSize = bufferSize - packedOffset;
                    byte[] result;
                    int dataType;
                    int pullSize = dataCache.Get(out result, realBufferSize, out dataType);
                    if (result != null)
                    {
                        int newSeq = dataCache.Seq;
                        for (int i=oldSeq; i < newSeq; i++)
                        {
                            Send(PackExcution(1, i), ip);
                        }

                        if (pullSize == result.Length)
                        {
                            UDPClientSocket socket = new UDPClientSocket(udp, null);
                            socket.Bind(dataCache.IP);

                            if (dataType == 0)
                            {
                                NetDataHandler.DataWrap dataWrap = new NetDataHandler.DataWrap(result);
                                netBase.DataHandler.Enqueue(socket, dataWrap);
                            }
                            else if (dataType == 1)
                            {
                                NetDataHandler.DataWrap dataWrap = new NetDataHandler.DataWrap(null, NetDataHandler.DataType.HeartBeats);
                                netBase.DataHandler.Enqueue(socket, dataWrap);
                            }
                        }
                    }
                }
            }
            else if (excution == 1) // 数据被接收
            {
                DataCache sendCache = GetSendCache(ip);
                if (sendCache != null)
                {
                    lock (sendCache)
                    {
                        if (sendCache.Remove(seq))
                        {
                            HandleBusyCache(sendCache);
                        }
                    }
                }
            }
            else if (excution == 2) // 数据丢失，重发数据
            {
                DataCache sendCache = GetSendCache(ip);
                if (sendCache != null)
                {
                    lock (sendCache)
                    {
                        byte[] sendData = sendCache.Get(seq);
                        if (sendData != null)
                        {
                            //Debug.LogError("===ReSendSeq: " + seq);
                            Send(sendData, sendCache.IP);
                        }
                    }

                }
            }
            else if (excution == 3) // 对方确认数据是否已经被接受
            {
                DataCache dataCache = GetDataCache(ip);

                if (seq >= dataCache.Seq)
                {
                    byte[] recvData = dataCache.Get(seq);
                    if (recvData == null)
                    {
                        Send(PackExcution(2, seq), ip);
                    }
                }
                else
                {
                    //Debug.LogError("Recved: " + seq);
                    Send(PackExcution(1, seq), ip);
                }
            }
        }

        public DataCache GetDataCache(EndPoint ip)
        {
            string key = ip.ToString();

            DataCache dataCache;
            lock (dataCaches)
            {
                if (!dataCaches.TryGetValue(key, out dataCache))
                {
                    dataCache = new DataCache(ip);
                    dataCaches.Add(key, dataCache);
                }
            }

            return dataCache;
        }

        public ConcurrentQueue<byte[]> GetBusyCache(EndPoint ip)
        {
            string key = ip.ToString();

            ConcurrentQueue<byte[]> busyCache;
            lock (busyCaches)
            {
                if (!busyCaches.TryGetValue(key, out busyCache))
                {
                    busyCache = new ConcurrentQueue<byte[]>();
                    busyCaches.Add(key, busyCache);
                }
            }

            return busyCache;
        }

        public DataCache GetSendCache(EndPoint ip)
        {
            string key = ip.ToString();

            DataCache sendCache;
            lock (sendCaches)
            {
                if (!sendCaches.TryGetValue(key, out sendCache))
                {
                    sendCache = new DataCache(ip);
                    sendCaches.Add(key, sendCache);
                }
            }

            return sendCache;
        }

        public override void ClearCaches(string ip)
        {
            string key = ip;
            lock (dataCaches)
            {
                dataCaches.Remove(key);
            }
            lock (sendCaches)
            {
                sendCaches.Remove(key);
            }
            lock (busyCaches)
            {
                busyCaches.Remove(key);
            }
            lock (heartBeatsCheckers)
            {
                HeartBeatsChecker heartBeatsChecker;
                if (heartBeatsCheckers.TryGetValue(key, out heartBeatsChecker))
                {
                    heartBeatsChecker.Close();
                    heartBeatsCheckers.Remove(key);
                }
            }
            UnityEngine.Debug.Log("ClearCaches" + ip);
        }

        public override void Close(int code = 0)
        {
            base.Close(code);
            sendConfirmTimer.Release();

            lock (heartBeatsCheckers)
            {
                int count = heartBeatsCheckers.Count;
                for (int index = 0; index < count; index++)
                {
                    heartBeatsCheckers.Get(index).Close();
                }
            }
            heartBeatsSender?.Stop();
        }

        protected Timer heartBeatsSender;
        public void SetHeartBeats(float interval)
        {
            heartBeatsSender?.Stop();
            if (interval > 0)
            {
                heartBeatsSender = new Timer().FrequencySetter(interval).PersistentSetter(true).TagSetter("ReliableOrderUDPClient_HeartBeatsSender").Invoke(SendHeartBeats);
            }
            else
            {
                heartBeatsSender = null;
            }
        }

        public void FirstDataHandleAction(object[] actionParams)
        {
            EndPoint ip = actionParams[0] as EndPoint;
            SetHeartBeatsChecker(ip);
        }

        public void SetHeartBeatsChecker(float interval)
        {
            heartBeatsCheckerInterval = interval;
            lock (sendCaches)
            {
                int count = sendCaches.Count;
                for (int index = 0; index < count; index++)
                {
                    DataCache sendCache = sendCaches.Get(index);
                    SetHeartBeatsChecker(sendCache.IP);
                }
            }
        }

        private void SetHeartBeatsChecker(EndPoint ip)
        {
            if (heartBeatsCheckerInterval <= 0)
            {
                return;
            }

            lock (heartBeatsCheckers)
            {
                string key = ip.ToString();
                HeartBeatsChecker heartBeatsChecker;
                if (!heartBeatsCheckers.TryGetValue(key, out heartBeatsChecker))
                {
                    heartBeatsChecker = new HeartBeatsChecker(ip, heartBeatsCheckerInterval);
                    heartBeatsChecker.Register(ClearCaches);
                    heartBeatsCheckers.Add(key, heartBeatsChecker);
                }
            }
        }

        private HeartBeatsChecker GetHeartBeatsChecker(string key)
        {
            HeartBeatsChecker heartBeatsChecker;
            lock (heartBeatsCheckers)
            {
                if (heartBeatsCheckers.TryGetValue(key, out heartBeatsChecker))
                {
                    heartBeatsChecker.Reset();
                }
            }

            return heartBeatsChecker;
        }

        private byte[] GetHeartBeatsData()
        {
            return new byte[] {0, 0, 0, 1, 1, 0 };
        }

        public void SendHeartBeats()
        {
            lock (sendCaches)
            {
                int count = sendCaches.Count;
                for (int index = 0; index < count; index++)
                {
                    DataCache sendCache = sendCaches.Get(index);
                    TargetIP = sendCache.IP;
                    byte[] hbd = GetHeartBeatsData();
                    SendOrigin(hbd, hbd.Length);
                }
            }
        }
    }
}

